package picobot.map;

import java.util.HashMap;

import java.util.Iterator;
import java.util.Set;

import picobot.interfaces.core.IMap;

/**
 * a class to represent a map
 */
public class Map implements IMap {

	private HashMap<Integer[], Boolean> map = new HashMap<Integer[], Boolean>();

	private final static String LINE_SEPERATOR = System
			.getProperty("line.separator");

	/**
	 * the constructor for this class which takes a hashmap
	 * 
	 * @param map
	 *            is the hashmap that is taken
	 */
	public Map(HashMap<Integer[], Boolean> map) {
		super();
		this.map = map;
	}

	@Override
	public int getWidth() {
		Set<Integer[]> coordinates = map.keySet();
		Iterator<Integer[]> iterator = coordinates.iterator();

		int highestX = 0;

		Integer[] current;

		while (iterator.hasNext()) {
			current = iterator.next();

			if (current[0] > highestX) {
				highestX = current[0];
			}
		}
		return highestX + 1;
	}

	@Override
	public int getHeight() {
		Set<Integer[]> coordinates = map.keySet();
		Iterator<Integer[]> iterator = coordinates.iterator();

		int highestY = 0;

		Integer[] current;

		while (iterator.hasNext()) {
			current = iterator.next();

			if (current[1] > highestY) {
				highestY = current[1];
			}
		}
		return highestY + 1;
	}

	@Override
	public String getCellKind(int x, int y) {
		Integer[] coordinates = new Integer[2];
		coordinates[0] = x;
		coordinates[1] = y;

		boolean cell = gets(coordinates);
		String cellType = "FREE";

		if (cell) {
			cellType = "WALL";
		}

		return cellType;
	}

	@Override
	public String toString() {
		String stringRepr = "";
		Integer[] coordinates = new Integer[2];

		int mapHeight = this.getHeight() - 1;
		int mapWidth = this.getWidth() - 1;

		for (int y = mapHeight; y >= 0; y--) {
			for (int x = 0; x <= mapWidth; x++) {
				coordinates[0] = x;
				coordinates[1] = y;

				try {
					if (gets(coordinates)) {
						stringRepr += "#";
					} else {
						stringRepr += " ";
					}
				} catch (NullPointerException e) {
					// if an error occurs add a free cell
					// an error will occur in the public test where no free cell
					// is placed
					stringRepr += " ";
				}

			}
			if (y != 0) {
				stringRepr += LINE_SEPERATOR;
			}
		}

		return stringRepr;

	}

	@Override
	public boolean isCompletelySurrounded() {
		int height = this.getHeight();
		int width = this.getWidth();
		boolean val = true;

		for (int i = 0; i < height; i++) {
			if (this.getCellKind(0, i).equals("FREE")) {
				val = false;
				break;
			}
			if (this.getCellKind(height - 1, i).equals("FREE")) {
				val = false;
				break;
			}
		}

		for (int j = 0; j < width; j++) {
			if (this.getCellKind(j, 0).equals("FREE")) {
				val = false;
				break;
			}
			if (this.getCellKind(j, width - 1).equals("FREE")) {
				val = false;
				break;
			}
		}
		return val;
	}

	@Override
	public int getNumberOfWalls() {
		String stringMap = this.toString();
		int nOfW = 0;

		for (int i = 0; i < stringMap.length(); i++) {
			if (stringMap.charAt(i) == '#') {
				nOfW += 1;
			}
		}
		return nOfW;
	}

	/**
	 * a method to get the value to which the given integer array is mapped
	 * 
	 * @param coordinates
	 *            is the integer array the value ants to be known
	 * @return the value of the given integer array
	 */
	public boolean gets(Integer[] coordinates) {
		Integer[] key = null;
		Set<Integer[]> keys = map.keySet();
		Iterator<Integer[]> iterator = keys.iterator();

		Integer[] current;

		while (iterator.hasNext()) {
			current = iterator.next();

			if (current[0] == coordinates[0] && current[1] == coordinates[1]) {
				key = current;
				break;
			}
		}

		boolean value = map.get(key);
		return value;
	}

	// --- the method isAConnectedMaze and it's supporting methods ---

	@Override
	public boolean isAConnectedMaze() {
		int height = this.getHeight();
		int width = this.getWidth();
		boolean isMaze = true;

		// iterate through all cells
		for (int y = height - 2; y >= 1; y--) {
			for (int x = 1; x < width - 1; x++) {

				// check if the cell is empty, i.e. free
				if (this.getCellKind(x, y).equals("FREE")) {

					// check if an empty cell has an empty cell to the north and
					// to the east,
					if (this.getCellKind(x, y + 1).equals("FREE")
							& this.getCellKind(x + 1, y).equals("FREE")) {
						// then it mustn't have an empty cell to the north-east,
						// too
						if (this.getCellKind(x + 1, y + 1).equals("FREE")) {
							// thus the map is no connected maze
							isMaze = false;
						}
					}

					// do the same as above, but check the north and the west of
					// an empty cell
					if (this.getCellKind(x, y + 1).equals("FREE")
							& this.getCellKind(x - 1, y).equals("FREE")) {
						if (this.getCellKind(x - 1, y + 1).equals("FREE")) {
							isMaze = false;
						}
					}

					// do the same as above, but check the south and the east of
					// an empty cell
					if (this.getCellKind(x, y - 1).equals("FREE")
							& this.getCellKind(x + 1, y).equals("FREE")) {
						if (this.getCellKind(x + 1, y - 1).equals("FREE")) {
							isMaze = false;
						}
					}

					// do the same as above, but check the south and the west of
					// an empty cell
					if (this.getCellKind(x, y - 1).equals("FREE")
							& this.getCellKind(x - 1, y).equals("FREE")) {
						if (this.getCellKind(x - 1, y - 1).equals("FREE")) {
							isMaze = false;
						}
					}

				}
			}

		}

		// return isMaze, if not one of the checks above is the case, the map is
		// a connected maze,
		// and true is returned. Otherwise the map cannot be a connected maze
		// and false is returned
		return isMaze;
	}
}
